// -----------------------------------------------------------------------------
// This source file is part of Matrix Platform
// 	(Universal .NET Software Development Platform)
// For the latest info, see http://www.matrixplatform.com
// 
// Copyright (c) 2009-2010, Ingenious Ltd
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------------------------------------------
using System;
using System.Threading;
using Matrix.Common.Core.Serialization;

#if Matrix_Diagnostics
    using Matrix.Common.Diagnostics;
#endif

namespace Matrix.Common.Sockets.Common
{
    /// <summary>
    /// Extends the communicator class by adding keep alive functionality.
    /// </summary>
    public class SocketCommunicatorEx : SocketCommunicator
    {
        bool _keepAlive = false;

        /// <summary>
        /// Keep alive will send, each few seconds, a ping and wait for a ping back, in case
        /// nothing comes in, declare the connection dead.
        /// </summary>
        public bool KeepAlive
        {
            get { return _keepAlive; }
            set 
            { 
                _keepAlive = value;
                
                if (value)
                {
                    ConstructTimer();
                }
                else
                {
                    ReleaseTimer();
                }
            }
        }

        Timer _timer = null;

        readonly TimeSpan KeepAliveTimerInterval = TimeSpan.FromSeconds(45);
        readonly TimeSpan KeepAliveTimeoutInterval = TimeSpan.FromSeconds(160);
        
        DateTime _lastMessageReceivedTime = DateTime.Now;

        /// <summary>
        /// Constructor.
        /// </summary>
        public SocketCommunicatorEx(ISerializer serializer)
            : base(serializer)
        {
            KeepAlive = false;
        }

        public override void Dispose()
        {
            ReleaseTimer();
            base.Dispose();
        }

        protected void ConstructTimer()
        {
            ReleaseTimer();
            lock (_syncRoot)
            {
                if (_timer != null)
                {
#if Matrix_Diagnostics
                    Monitor.Error("Timer already constructed.");
#endif
                    return;
                }

                _timer = new Timer(TimerCallbackMethod, null,
                                   TimeSpan.FromSeconds(1.5), KeepAliveTimerInterval);
            }
        }

        protected void ReleaseTimer()
        {
            Timer timer = _timer;
            _timer = null;
            if (timer != null)
            {
                timer.Dispose();
            }
        }

        internal override void ProcessSystemMessage(SystemMessage message)
        {// Received a system message.

#if Matrix_Diagnostics
            InstanceMonitor monitor = Monitor;
            if (monitor != null && monitor.IsReportAllowed)
            {
                monitor.Info(string.Format("Processing system message [{0}].", message.ToString()));
            }
#endif

            if (message.Type == SystemMessage.TypeEnum.KeepAlive
                && this.KeepAlive == false)
            {// Make sure to activate keep alive on this side too.
#if Matrix_Diagnostics
                monitor.Warning("Received KeepAlive, and KeepAlive not active on this client, auto-activating.");
#endif
                this.KeepAlive = true;
            }

            _lastMessageReceivedTime = DateTime.Now;
        }

        /// <summary>
        /// TODO: Possible bug: behavior observed, the system "fell asleep" for 4 minutes
        /// and when it woke up, the timer started 5 threads at the same time on this method.
        /// 
        /// They all raised disconnected events, but the remaining of the system seemed to be OK ?!
        /// 
        /// This happened on a Windows machine that has been running with no restart for a few weeks.
        /// </summary>
        /// <param name="state"></param>
        private void TimerCallbackMethod(object state)
        {
            if (_keepAlive == false)
            {
                ReleaseTimer();
                return;
            }

            if (IsConnected)
            {
                // Send an empty system message to keep alive.
                SendAsync(new SystemMessage(), null);
            }

            if (IsConnected && DateTime.Now - _lastMessageReceivedTime > KeepAliveTimeoutInterval)
            {// Signal disconnection.
#if Matrix_Diagnostics
                InstanceMonitor monitor = Monitor;
                if (monitor != null && monitor.IsReportAllowed)
                {
                    monitor.Info("Disconnecting due to no activity timeout.");
                }
#endif
             
                DisconnectAsync();
            }
        }


    }
}
